package org.zengrong.net {
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.net.URLVariables;
	import org.zengrong.utils.ObjectUtil;

	/**
	 * 构造函数，提供两个Function参数供Load成功或者失败的时候调用
	 * @param $comple 载入成功的时候调用的Function，必须接受一个Object或Array参数。
	 * URL--[url,url],Variable--[{name:"yu",id:13},{name:"feng",id:12}]
	 * URL--url,Variable--{name:"yu"}
	 * 
	 * @param $error 载入错误的时候调用的Function，必须接受一个Object参数。
	 * Object结构为：{returnData:提交的时候要求返回的数据,message:错误信息}
	 *
	 * 加载完成返回数据completeHandler(obj:*)
	 */
	public class HTTPLoader {
		protected var _fun_loadComple:Function;  //成功后的回调函数
		protected var _fun_loadError:Function; //错误时的回调函数
		protected var _method:String = 'GET';
		protected var _dataFormat:String = 'text';
		protected var _loading:Boolean;        //是否正在载入。这个变量保证同一时间只能有一次或者一组载入。
		protected var _multi:Boolean;          //是否是多文件载入
		protected var _loader:URLLoader;
		
		protected var _submitVar:Object;       //每次提交都需要的
		protected var _returnVar:Object;       //每次提交的时候需要原样返回的参数。这些参数在服务器返回的时候会原样提供
		protected var _curSubmitVar:URLVariables;  //保存当次提交的变量
		protected var _curReturnVar:Object;    //保存当次提交需要返回的参数。
		protected var _curUrl:String;          //保存当次提交的URL
		protected var _urls:Array;             //多文件载入保存每次载入的路径
		
		//如果是多文件载入，这个变量保存每次提交的时候需要返回的参数。所有的多重载入提供的参数都将统一视为需要返回的参数
		protected var _submitVars:Array;
		protected var _results:Array;          //保存多文件载入时候返回的值
		
		public function HTTPLoader($comple:Function, $error:Function){
			init();
			_fun_loadComple = $comple;
			_fun_loadError  = $error;
		}
		
		/*初始化*/
		protected function init():void {
			_loader = new URLLoader();
			_loader.dataFormat = _dataFormat;
			_loading = false;
			_multi = false;
			addEvent();
		}

		/**
		 * 额外添加variable
		 * 不仅会传给服务端，同时也会在返回的时候提供。
		 * 如果使用的是单载入，那么每次载入完毕后，这些参数会被清空。
		 * 如果使用的是多重载入，那么使用此方法添加的参数，在每次提交和返回的时候都会提供。
		 * @param $key
		 * @param $value
		 */
		public function addReturnVar($key:String, $value:*):void {
			if (!_returnVar)
				_returnVar = {};
			_returnVar[$key] = $value;
			addVariable($key, $value);
		}

		/**
		 * 递给服务器的参数信息
		 * 如果使用的是单载入，那么每次载入完毕后，这些参数会被清空。
		 * 如果使用的是多重载入，那么使用此方法添加的参数，在每次提交的时候都会提供。
		 * @param $key
		 * @param $value
		 */
		public function addVariable($key:String, $value:*):void {
			if (!_submitVar)
				_submitVar = {};
			_submitVar[$key] = $value;
		}

		/**
		 * 开始载入URL地址。
		 * @param $url 要载入的地址。地址可以是String或者包含字符串的Array。
		 * 1.如果是String，将其作为载入URL对待；
		 * 2.如果是Array，将其作为包含需要批量载入的URL对待。
		 * 
		 * @param $requestVar 载入时要传递的参数。参数可以是Object或者Array。
		 * 如果$url是Array，则这里也必须提供与$url元素相同的Array，Array的每个元素应该是Object。
		 * 
		 */
		public function load($url:*, $requestVar:* = null):void {
			if (!$url)
				throw new ArgumentError('必须提供URL参数!');
			if ($url && ($url is Array) && ($url as Array).length == 0)
				throw new ArgumentError('提供的URL数组元素数量为0!');
			//如果正在载入，就将要载入的url和值加入队列中，但不执行
			if (_loading){
				//不能确定单载入是否定义了_urls。第一次单载入的时候，肯定是没有_urls的，但单载入有可能在第一次载入没有完成的时候被连续调用
				//因此需要初始化_urls，但如果是_multi状态的话，在loading的时候，_urls肯定是有值的
				if (!_multi && !_urls){
					_urls = [];
					_results = [];
				}
				_urls = _urls.concat($url);
				//在loading的情况下载入，_multi要设置成true
				_multi = true;
				//这个和urls的情况类似，不过要判断$requestVar是否设置
				if ($requestVar){
					if (!_submitVars)
						_submitVars = [];
					_submitVars = _submitVars.concat($requestVar);
				}
				return;
			}
			if ($url is String){       //单载入
				_loading = true;
				_multi   = false;
				perform($url, $requestVar);
			} else if ($url is Array){ //多重载入
				_loading = true;
				_multi = true;
				_results = [];
				_urls = $url as Array; //保存提供的数组参数
				_submitVars = $requestVar as Array;
				
				if (_submitVars){      //如果提供了参数，就将参数传入
					perform(_urls.shift(), _submitVars.shift());
				} else {
					perform(_urls.shift());
				}
			}
		}
		/**
		 * 处理提供的url及参数
		 * 执行加载  _loader.load(request);
		 */
		protected function perform($url:String, $var:Object = null):void {
			_curUrl = $url;
			//如果提供了每次提交参数，就将参数中的值全部加入要提交的变量中
			if (_submitVar){
				if (!_curSubmitVar)
					_curSubmitVar = new URLVariables();
				for (var _key:String in _submitVar){
					_curSubmitVar[_key] = _submitVar[_key];
				}
			}
			
			//如果本次提交提供了参数，就将参数中的所有值加入到本次要提交的变量中
			if ($var){
				if (!_curSubmitVar)
					_curSubmitVar = new URLVariables();
				for (var __key2:String in $var){
					_curSubmitVar[__key2] = $var[__key2];
				}
				//如果是多重载入，就将本次提交的所有变量值都作为要返回的变量进行保存
				if (_multi)
					_curReturnVar = $var;
			}
			var _request:URLRequest = new URLRequest(_curUrl);
			_request.method = _method;
			
			if (_curSubmitVar)
				_request.data = _curSubmitVar;  //传入参数
			_loader.dataFormat = _dataFormat;   //加载格式
			_loader.load(_request);
		}

		/*事件的侦听*/
		protected function addEvent():void {
			_loader.addEventListener(IOErrorEvent.IO_ERROR, handler_error);
			_loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handler_error);
			_loader.addEventListener(Event.COMPLETE, handler_complete);
		}
		
		/*移除事件的侦听*/
		protected function removeEvent():void {
			if (_loader){
				_loader.removeEventListener(IOErrorEvent.IO_ERROR, handler_error);
				_loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, handler_error);
				_loader.removeEventListener(Event.COMPLETE, handler_complete);
			}
		}
        
		/**
		 * 创建返回的时候加入的参数 
		 * @return 
		 */		
		private function createReturnVar():Object {
			var _returnData:Object = null;
			//如果提供了返回变量，就将返回变量作为参数加入到返回值中
			if (_returnVar)
				_returnData = ObjectUtil.clone(_returnVar);
			
			//如果提供了每次返回的变量（实际上就是load的时候提供的所有变量），就将这些变量加入到返回值的returnData中
			if (_curReturnVar){
				if (!_returnData)
					_returnData = {};
				for (var __key:String in _curReturnVar)
					_returnData[__key] = _curReturnVar[__key];
			}
			return _returnData;
		}

		/**清理变理，释入内存*/
		private function clearVar():void {
			_curReturnVar = null;
			_curSubmitVar = null;
			_returnVar = null;
			_submitVar = null;
			_loading = false;
			_results = null;
		}

		/**
		 * url IO出错
		 * 载入错误，就立即将错误返回
		 */
		protected function handler_error(evt:ErrorEvent):void {
			var _result:Object = {};
			_result.returnData = createReturnVar();
			_result.message = '载入【' + _curUrl + '】失败，错误信息：' + evt.toString();
			_fun_loadError.call(null, _result);
			
			//对于多重载入，即使载入错误，依然要继续载入。但检测的时候，不将返回输入加入数组中。
			//也就是说最终返回的结果数组，将不包含这次载入错误的数据。
			if (_multi)
				checkMultiLoadDone(false);
			else
				clearVar();
		}
		
		/**载入完成*/
		protected function handler_complete(evt:Event):void {
			if (_multi){			//如果是多重载入，就是用数组保存
				checkMultiLoadDone();
			} else {				//如果是单次载入，就直接返回result的值
				var _result:Object = {};
				_result.returnVar  = createReturnVar();
				_result.resultData = loaderData; //真实的返回值
				_result.url = _curUrl;           //提交的url地址
				clearVar();
				
				/**
				 * 此句必须放在最后，因为如果在_fun_loadDone中再次调用load
				 * 就会影响_urls的值，导致跳过某些载入
				 */
				_fun_loadComple.call(null, _result);
			}
		}

		/**
		 * 检测多重载入是否全部完成。
		 * @param $addReturn 值为true，则将返回的结果加入数组；否则不加入数组。
		 */
		private function checkMultiLoadDone($addResult:Boolean = true):void {
			if ($addResult){
				//__result是一次提交返回的值
				var _result:Object = {};
				_result.returnVar  = createReturnVar();//参数
				_result.resultData = loaderData;       //真实的返回值
				_result.url = _curUrl;  		       //提交的url地址
				_results.push(_result); 		       //将返回的值加入数组
			}
			//如果载入没有完毕，就继续载入
			if (_urls.length > 0){
				_curReturnVar = null;
				_curSubmitVar = null;
				//再次调用载入
				perform(_urls.shift(), _submitVars ? _submitVars.shift() : null);
			}  else { //如果载入完毕就调用函数，传递结果数组
				var _resultArr:Array = _results.concat();
				clearVar();
				_urls = null;
				_submitVars = null;
				
				//call必须最后调用，以避免call中再调用load导致不可预见的错误。
				//所以要将_results进行复制，然后清空变量。
				_fun_loadComple.call(null, _resultArr); 
			}
		}
		
		public function get loading():Boolean {
			return _loading;
		}
		
		public function get method():String {
			return _method;
		}
		
		public function set method($method:String):void {
			_method = $method;
		}
		
		public function get dataFormat():String {
			return _dataFormat;
		}
		
		public function set dataFormat($format:String):void {
			_dataFormat = $format;
		}
		
		/**
		 * 返回需要的真实数据。返回的数据可能是各种格式，例如二进制流等等。
		 * 子类可以覆盖这个方法，在将返回的数据保存之前处理一下，获得需要的格式。
		 */
		protected function get loaderData():* {
			return _loader.data;
		}
		
		/**销毁*/
		public function destroy():void {
			clearVar();
			removeEvent();
			_loader = null;
			_fun_loadComple = null;
			_fun_loadError = null;
		}
	}
}